package me.itsoo.artemis.framework.ioc.factory;

import lombok.extern.slf4j.Slf4j;
import me.itsoo.artemis.framework.core.util.AnnotationUtils;
import me.itsoo.artemis.framework.core.util.ReflectUtils;
import me.itsoo.artemis.framework.core.util.StringUtils;
import me.itsoo.artemis.framework.ioc.bean.BeanDefinition;
import me.itsoo.artemis.framework.ioc.bean.BeanInitializer;
import me.itsoo.artemis.framework.ioc.exception.CreateBeanException;
import me.itsoo.artemis.framework.ioc.exception.NoSuchBeanException;
import me.itsoo.artemis.framework.ioc.lang.Inject;
import me.itsoo.artemis.framework.ioc.util.BeanUtils;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

/**
 * DefaultBeanFactory
 *
 * @author zxy
 * @version 0.0.1
 * @since 2023/6/5
 */
@Slf4j
@SuppressWarnings("unchecked")
public class DefaultBeanFactory implements CacheableBeanFactory, BeanDefinitionRegistry {

    private final Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>(32);

    private final Map<String, Object> beanCreatingCache = new HashMap<>(32);

    private final Map<String, Object> beanFinishedCache = new HashMap<>(32);

    @Override
    public void registerBeanDefinition(String beanName, Class<?> beanClass) {
        registerBeanDefinition0(new BeanDefinition(beanName, beanClass));
    }

    /**
     * 注册 BeanDefinition
     *
     * @param bd BeanDefinition
     */
    private void registerBeanDefinition0(BeanDefinition bd) {
        beanDefinitionMap.put(bd.getBeanName(), bd);
    }

    @Override
    public void removeBeanDefinition(String beanName) {
        beanDefinitionMap.remove(beanName);
    }

    @Override
    public BeanDefinition getBeanDefinition(String beanName) {
        return beanDefinitionMap.get(beanName);
    }

    @Override
    public Set<BeanDefinition> getAllBeanDefinitions() {
        return new LinkedHashSet<>(beanDefinitionMap.values());
    }

    @Override
    public Object getBean(String beanName) throws NoSuchBeanException {
        final Object result = beanFinishedCache.get(beanName);
        if (Objects.isNull(result)) {
            synchronized (DefaultBeanFactory.class) {
                final BeanDefinition bd = getBeanDefinition(beanName);
                return createBean(bd);
            }
        }

        return result;
    }

    @Override
    public <T> T getBean(Class<T> beanClass) throws NoSuchBeanException {
        return (T) getBean(BeanUtils.getBeanName(beanClass));
    }

    @Override
    public boolean containsBean(String beanName) {
        return beanDefinitionMap.containsKey(beanName);
    }

    @Override
    public String[] getAllBeanNames() {
        return beanFinishedCache.keySet().toArray(new String[0]);
    }

    /**
     * 初始化 Bean 回调，子类可重写该方法
     *
     * @param obj Object
     * @param bd  BeanDefinition
     */
    protected void initializingBean(Object obj, BeanDefinition bd) {
        if ((obj instanceof BeanInitializer)) {
            ((BeanInitializer) obj).init();
        }
    }

    /**
     * 创建 Bean
     *
     * @param bd  BeanDefinition
     * @param <T> T
     * @return T
     * @throws CreateBeanException CreateBeanException
     */
    private <T> T createBean(BeanDefinition bd) throws CreateBeanException {
        // TODO: 当前只支持字段方式注入依赖
        bd.setFieldType();
        final Object obj = createBean0(bd);

        prepareDependencies(bd, bd);
        processDependencies(obj, bd);

        // 对最外层首个 Bean 创建状态补偿
        recomputeBeanInstanceStatus(bd);
        return (T) obj;
    }

    /**
     * 创建 Bean
     *
     * @param bd  BeanDefinition
     * @param <T> T
     * @return T
     * @throws CreateBeanException CreateBeanException
     */
    private <T> T createBean0(BeanDefinition bd) throws CreateBeanException {
        final Object obj = ReflectUtils.newInstance(bd.getBeanClass());
        if (Objects.isNull(obj)) {
            throw new CreateBeanException(bd.getBeanClass());
        }

        beanCreatingCache.put(bd.getBeanName(), obj);
        beanFinishedCache.remove(bd.getBeanName());
        return (T) obj;
    }

    /**
     * 解析 Bean 依赖
     *
     * @param bd   BeanDefinition
     * @param curr BeanDefinition
     */
    private void prepareDependencies(BeanDefinition bd, BeanDefinition curr) {
        for (BeanDefinition innerBd : getAllDependencies(curr.getBeanClass())) {
            innerBd = computeBeanDefinitionIfAbsent(innerBd);

            curr.appendDependency(innerBd);
            curr.setWaiting();
            bd.setWaiting();
            // 计算循环依赖关系（只记录循环依赖的首个节点）
            bd.setCircularDependency(bd.isCircularDependency() || Objects.equals(bd, innerBd));

            if (!curr.isCircularDependency()) {
                prepareDependencies(bd, innerBd);
            }
        }
    }

    /**
     * 处理 Bean 依赖
     *
     * @param obj 源对象
     * @param bd  BeanDefinition
     */
    private void processDependencies(Object obj, BeanDefinition bd) {
        recomputeBeanInstanceStatus(bd);

        for (BeanDefinition dependencyBd : bd.getBeanDependencies()) {
            final String dependencyBeanName = dependencyBd.getBeanName();
            Object bean = beanFinishedCache.get(dependencyBeanName);
            if (Objects.isNull(bean)) {
                bean = beanCreatingCache.get(dependencyBeanName);
                if (Objects.isNull(bean)) {
                    bean = createBean0(dependencyBd);
                }
            }

            final Field f = ReflectUtils.getField(bd.getBeanClass(), dependencyBeanName);
            ReflectUtils.setFieldValue(f, obj, bean);
            // 非循环依赖 且 非创建中的 Bean 递归处理依赖
            if (!bd.isCircularDependency() || !dependencyBd.isCreating()) {
                ReflectUtils.setFieldValue(f, obj, bean);
                processDependencies(bean, dependencyBd);
            }
        }

        beanFinishedCache.put(bd.getBeanName(), obj);
        beanCreatingCache.remove(bd.getBeanName());
        initializingBean(obj, bd);
    }

    /**
     * 获取依赖 BeanDefinition
     *
     * @param beanClass Class
     * @return Set
     */
    private Set<BeanDefinition> getAllDependencies(Class<?> beanClass) {
        final Field[] fields = beanClass.getDeclaredFields();
        final Set<BeanDefinition> result = new LinkedHashSet<>(fields.length);

        for (Field f : fields) {
            final Inject ann = AnnotationUtils.findAnnotation(f, Inject.class);
            if (Objects.nonNull(ann)) {
                final Class<?> dependencyBeanClass = ReflectUtils.getGenericClass(f.getGenericType());
                final String beanName = StringUtils.defaultIfBlank(ann.value(), f.getName());
                final BeanDefinition bd = new BeanDefinition(beanName, dependencyBeanClass);

                BeanUtils.getAliasNames(beanName, f.getName()).forEach(bd::setAliasName);
                bd.setFieldType();
                result.add(bd);
            }
        }

        return result;
    }

    /**
     * 如果缓存池不存在则注册 BeanDefinition
     *
     * @param bd BeanDefinition
     * @return BeanDefinition
     */
    private BeanDefinition computeBeanDefinitionIfAbsent(BeanDefinition bd) {
        final BeanDefinition result = getBeanDefinition(bd.getBeanName());
        if (Objects.isNull(result)) {
            registerBeanDefinition0(bd);
            return bd;
        }

        return result;
    }

    /**
     * 重新计算 Bean 创建状态
     *
     * @param bd BeanDefinition
     */
    private void recomputeBeanInstanceStatus(BeanDefinition bd) {
        if (beanCreatingCache.containsKey(bd.getBeanName())) {
            bd.setCreating();
        }

        if (beanFinishedCache.containsKey(bd.getBeanName())) {
            bd.setFinished();
        }
    }
}
